import LogUtils from './LogUtils';
import PromiseUtils from './PromiseUtils';
import Const from './const';


Function.prototype.asynchronize = function() {
    let func = this;
    return async function() {
        return func.apply(this, arguments);
    };
};

Function.prototype.promisify = function(options = {}) {
    let func = this;
    return function(...args) {
        return new Promise(function(resolve, reject) {
            func(...args, function(err, ...args2) {
                if (options.all) {
                    if (options.single) {
                        resolve(err);
                    } else {
                        resolve(Array.from(arguments));
                    }
                } else {
                    if (err) {
                        reject(err);
                    } else {
                        if (options.single) {
                            resolve(args2[0]);
                        } else {
                            resolve(args2);
                        }
                    }
                }
            });
        });
    };
};

Promise.prototype.done = function(onCatch) {
    this.then().catch(onCatch || LogUtils.error);
};

Object.defineProperties(Array.prototype, {
    parallelForeach: {
        writable: true,
        enumerable: false,
        value: function(callback) {
            let val = this.map(callback).map(e => PromiseUtils.flattenValue(e));
            return Promise.all(val);
        }
    },
    mapToObject: {
        writable: true,
        enumerable: false,
        value: function(
            getKey = e => e === undefined ? undefined : e.key || e[0],
            getValue = e => e === undefined ? undefined : e.value || e[1], {
                skipUndefinedKey = true,
                skipUndefinedValue = true,
                skipNullKey = true,
                skipNullValue = true,
            } = {}
        ) {
            return this.reduce((out, e) => {
                let key = getKey(e);
                if (key === undefined && skipUndefinedKey) {
                    return out;
                }
                if (key === null && skipNullKey) {
                    return out;
                }
                let value = getValue(e);
                if (value === undefined && skipUndefinedValue) {
                    return out;
                }
                if (value === null && skipNullValue) {
                    return out;
                }
                out[key] = value;
                return out;
            }, {});
        }
    }
});


Array.range = function(start, end, step = 1) {
    let out = [];
    for (let i = start; i < end; i += step) {
        out.push(i);
    }
    return out;
};


Object.defineProperties(Object.prototype, {
    map: {
        writable: true,
        enumerable: false,
        value: function(mapper = e => e) {
            let out = {};
            for (let i in this) {
                out[i] = mapper(this[i], i, this);
            }
            return out;
        },
    },
    forEach: {
        writable: true,
        enumerable: false,
        value: function(func = e => e) {
            for (let i in this) {
                func(this[i], i, this);
            }
        },
    },
    filter: {
        writable: true,
        enumerable: false,
        value: function(filter) {
            let out = {};
            for (let i in this) {
                if (filter(this[i], i, this)) {
                    out[i] = this[i];
                }
            }
            return out;
        }
    },
    defaultFilter: {
        writable: true,
        enumerable: false,
        value: function() {
            return this.filter((v, k, o) => o.hasOwnProperty(k) && v != null);
        }
    },
    deepCopy: {
        writable: true,
        enumerable: false,
        value: function({ 
            maxDepth = Const.MaxDeepEqualDepth, 
            depth = 1 
        } = {}) {
            if (depth > maxDepth || typeof this !== 'object') {
                return this;
            } else  {
                let o = this;
                if (this.defaultFilter) {
                    o = this.defaultFilter();
                } 
                return o.map(v => {
                    if (v.deepCopy) {
                        return v.deepCopy({ maxDepth, depth: depth + 1 });
                    } else {
                        return v;
                    }
                });
            }
        }
    },
    copy: {
        writable: true,
        enumerable: false,
        value: function() {
            return Object.assign({}, this);
        }
    }
});

require.plugin = function(key) {
    return require.dynamic(key);
};
